/*
 * Cilinder.cpp
 *
 *  Created on: 14/04/2013
 *      Author: gonzalo
 */

#include "Cilinder.h"

#include "../exceptions/ArgumentInvalidError.h"

#include <cmath>
#include <iostream>

void Cilinder::create_vertex_buffer(GLuint segments) {
	GLfloat phi;
	GLfloat cosPhi;
	GLfloat sinPhi;
	GLuint indice;

	vertex_buffer_size = segments * 2 + 2;

	vertex_buffer = new GLfloat[vertex_buffer_size * 3];

	// TODO usar GL_TRIANGLE_FAN

	// construyo un cilindro de altura 1 centrado en (0,0,0)

	GLfloat z_techo_cilindro = 0.5;
	GLfloat z_base_cilindro = -0.5;

	for (GLuint i = 0; i < segments; i++) {
		phi = 2 * PI * i / segments;
		cosPhi = cos(phi);
		sinPhi = sin(phi);
		indice = 6 * i;
		vertex_buffer[indice++] = cosPhi;
		vertex_buffer[indice++] = sinPhi;
		vertex_buffer[indice++] = z_base_cilindro;

		vertex_buffer[indice++] = cosPhi;
		vertex_buffer[indice++] = sinPhi;
		vertex_buffer[indice++] = z_techo_cilindro;
	}

	indice = (vertex_buffer_size - 2) * 3;
	vertex_buffer[indice++] = 0.0;
	vertex_buffer[indice++] = 0.0;
	vertex_buffer[indice++] = z_base_cilindro;

	vertex_buffer[indice++] = 0.0;
	vertex_buffer[indice++] = 0.0;
	vertex_buffer[indice++] = z_techo_cilindro;

}

void Cilinder::create_index_buffer(GLuint segments) {
	index_buffer_size = (2 * segments + 2);
	index_buffer = new GLuint[index_buffer_size];

	index_tapas_size = 6 * segments;
	index_tapas = new GLuint[index_tapas_size];

	for (GLuint i = 0; i < 2 * segments; i++) {
		index_buffer[i] = i;
	}
	index_buffer[2 * segments] = 0;
	index_buffer[2 * segments + 1] = 1;

	for (GLuint i = 0; i < segments; i++) {
		index_tapas[3 * i] = 2 * i;
		index_tapas[3 * i + 1] = (2 * i + 2) % (2 * segments);
		index_tapas[3 * i + 2] = vertex_buffer_size - 2;

		index_tapas[3 * (i + segments)] = 2 * i + 1;
		index_tapas[3 * (i + segments) + 1] = (2 * i + 3) % (2 * segments);
		index_tapas[3 * (i + segments) + 2] = vertex_buffer_size - 1;
	}
}

void Cilinder::create_normal_buffer(GLuint segments) {
	normal_buffer_size = index_buffer_size;
	normal_buffer = new GLfloat[3 * normal_buffer_size];
	normal_buffer_tapas_size = index_tapas_size;
	normal_buffer_tapas = new GLfloat[3 * normal_buffer_tapas_size];
	GLfloat phi;
	GLfloat cosPhi;
	GLfloat sinPhi;
	GLuint indice;

	for (GLuint i = 0; i < segments; i++) {
		phi = 2 * PI * i / segments;
		cosPhi = cos(phi);
		sinPhi = sin(phi);
		indice = 6 * i;
		normal_buffer[indice++] = cosPhi;
		normal_buffer[indice++] = sinPhi;
		normal_buffer[indice++] = 0.0;

		normal_buffer[indice++] = cosPhi;
		normal_buffer[indice++] = sinPhi;
		normal_buffer[indice++] = 0.0;
	}

	for (GLuint i = 0; i < 3 * segments; i++) {
		indice = 3 * i;
		normal_buffer_tapas[indice++] = 0.0;
		normal_buffer_tapas[indice++] = 0.0;
		normal_buffer_tapas[indice++] = -1.0;

	}

	for (GLuint i = 3 * segments; i < 6 * segments; i++) {
		indice = 3 * i;
		normal_buffer_tapas[indice++] = 0.0;
		normal_buffer_tapas[indice++] = 0.0;
		normal_buffer_tapas[indice++] = 1.0;

	}
}

void Cilinder::create_mesh(unsigned int segments) {
	DatosVertice datos;
	uint indice_base;
	uint indice_actual;
	size = (2 * segments + 2);
	size_tapa = (segments + 2);
	size_index_tapas = 2 * size_tapa;
	vertices = new DatosVertice[size + 2 * size_tapa];
	index_size = 4 * segments;
	indices = new unsigned int[index_size + size_index_tapas];

	for (uint i = 0; i < segments + 1; i++) {
		float phi;

		phi = 2 * M_PI * i / segments;
		if (segments == i) {
			phi = 0.0;
		}
		float cosPhi = cosf(phi);
		float sinPhi = sinf(phi);
		vertices[2 * i].position = glm::vec3(cosPhi, sinPhi, -0.5);
		vertices[2 * i].normal = glm::vec3(cosPhi, sinPhi, 0.0);
		vertices[2 * i].color = glm::vec3(cosPhi, sinPhi, 0.5);
		vertices[2 * i].tangent = glm::vec3(0.0, 0.0, 0.5);
		vertices[2 * i].texture = glm::vec2(sinf(phi / 8), 0.0);
		vertices[2 * i + 1].position = glm::vec3(cosPhi, sinPhi, 0.5);
		vertices[2 * i + 1].normal = glm::vec3(cosPhi, sinPhi, 0.0);
		vertices[2 * i + 1].color = glm::vec3(cosPhi, sinPhi, 0.5);
		vertices[2 * i + 1].tangent = glm::vec3(0.0, 0.0, 0.5);
		vertices[2 * i + 1].texture = glm::vec2(sinf(phi / 8), 1.0);
		//std::cout << sinf(phi / 8) << std::endl;
		if (segments == i) {
			vertices[2 * i].texture = glm::vec2(1.0, 0.0);
			vertices[2 * i + 1].texture = glm::vec2(1.0, 1.0);
		}
	}

	for (uint i = 0; i < segments; i++) {
		indices[4 * i] = 2 * i;
		indices[4 * i + 1] = 2 * i + 2;
		indices[4 * i + 2] = 2 * i + 3;
		indices[4 * i + 3] = 2 * i + 1;
	}

	indice_base = 2 * (segments + 1);

	vertices[indice_base].position = glm::vec3(0.0, 0.0, -0.5);
	vertices[indice_base].normal = glm::vec3(0.0, 0.0, -1.0);
	vertices[indice_base].color = glm::vec3(1.0, 1.0, 1.0);
	vertices[indice_base].tangent = glm::vec3(1.0, 0.0, 0.0);
	vertices[indice_base].texture = glm::vec2(0.5, 0.5);

	vertices[indice_base + segments + 2].position = glm::vec3(0.0, 0.0, 0.5);
	vertices[indice_base + segments + 2].normal = glm::vec3(0.0, 0.0, 1.0);
	vertices[indice_base + segments + 2].color = glm::vec3(1.0, 1.0, 1.0);
	vertices[indice_base + segments + 2].tangent = glm::vec3(1.0, 0.0, 0.0);
	vertices[indice_base + segments + 2].texture = glm::vec2(0.5, 0.5);

	indice_base++;

	for (uint i = 0; i < segments + 1; i++) {
		float phi;

		phi = 2 * M_PI * i / segments;
		if (segments == i) {
			phi = 0.0;
		}
		float cosPhi = cosf(phi);
		float sinPhi = sinf(phi);
		float cosPhiText = cosf(phi);
		float sinPhiText = sinf(phi);
		vertices[indice_base + i].position = glm::vec3(cosPhi, sinPhi, -0.5);
		vertices[indice_base + i].normal = glm::vec3(0.0, 0.0, -1.0);
		vertices[indice_base + i].color = glm::vec3(cosPhi, sinPhi, 0.0);
		vertices[indice_base + i].tangent = glm::vec3(1.0, 0.0, 0.0);
		vertices[indice_base + i].texture = glm::vec2(0.5 + 0.5 * cosPhi, 0.5 + 0.5 * sinPhi);

		vertices[indice_base + segments + 2 + i].position = glm::vec3(cosPhi, sinPhi, 0.5);
		vertices[indice_base + segments + 2 + i].normal = glm::vec3(0.0, 0.0, 1.0);
		vertices[indice_base + segments + 2 + i].color = glm::vec3(cosPhi, sinPhi, 0.0);
		vertices[indice_base + segments + 2 + i].tangent = glm::vec3(1.0, 0.0, 0.0);
		vertices[indice_base + segments + 2 + i].texture = glm::vec2(0.5 + 0.5 * cosPhi, 0.5 + 0.5 * sinPhi);
	}

	indice_actual = 4 * segments;
	indices[indice_actual] = indice_base - 1; // Centro de Triangle FAN

	indices[indice_actual + segments + 2] = indice_base + segments + 1; // Centro de Triangle FAN
	indice_actual++;
	for (uint i = 0; i < segments + 1; i++) {
		indices[indice_actual + i] = indice_base + i;
		indices[indice_actual + segments + 2 + i] = indice_base + segments + 2 + i;
	}

}

Cilinder::Cilinder(GLuint program, GLuint segments) :
		Figure(program) {
	if (segments <= 2) {
		throw ArgumentInvalidError("Cilinder: segments no puede ser < 3 (%d)", segments);
	}

	glGenBuffers(1, &vertex_buffer_object);
	glBindBuffer(GL_ARRAY_BUFFER, vertex_buffer_object);
	glGenBuffers(1, &index_buffer_object);
	glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, index_buffer_object);

	this->create_mesh(segments);

	glBufferData(GL_ARRAY_BUFFER, (size + 2 * size_tapa) * sizeof(DatosVertice), vertices, GL_STATIC_DRAW);
	glBufferData(GL_ELEMENT_ARRAY_BUFFER, (index_size + size_index_tapas) * sizeof(unsigned int), indices,
			GL_STATIC_DRAW);

	glBindBuffer(GL_ARRAY_BUFFER, 0);
	glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
//this->type = GL_TRIANGLES;
	this->type = GL_QUADS;
//}

	delete[] vertices;
	delete[] indices;

	this->vertices = NULL;
	this->indices = NULL;
}

Cilinder::~Cilinder() {
}

void Cilinder::draw(GLuint program, glm::mat4 & world_matrix) {
	GLint location;
	glm::mat4 matriz = world_matrix * this->model_matrix;

	int vertex = glGetAttribLocation(program, "vertex"); //0
	int normal = glGetAttribLocation(program, "normal"); //1
	int tangent = glGetAttribLocation(program, "tangent"); //2
	int color = glGetAttribLocation(program, "color"); //3
	int UV = glGetAttribLocation(program, "UV"); //4

	glBindBuffer(GL_ARRAY_BUFFER, vertex_buffer_object);
	glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, index_buffer_object);

	calculate_and_bind_normal_matrix(program,matriz);
	bind_model_matrix(program,matriz);

	glEnableVertexAttribArray(vertex);
	glVertexAttribPointer(vertex, 3, GL_FLOAT, GL_FALSE, sizeof(DatosVertice), 0);

	glEnableVertexAttribArray(normal);
	glVertexAttribPointer(normal, 3, GL_FLOAT, GL_FALSE, sizeof(DatosVertice), (void*) (3 * sizeof(float)));

	glEnableVertexAttribArray(tangent);
	glVertexAttribPointer(tangent, 3, GL_FLOAT, GL_FALSE, sizeof(DatosVertice), (void*) (6 * sizeof(float)));

	glEnableVertexAttribArray(color);
	glVertexAttribPointer(color, 3, GL_FLOAT, GL_FALSE, sizeof(DatosVertice), (void*) (9 * sizeof(float)));

	glEnableVertexAttribArray(UV);
	glVertexAttribPointer(UV, 2, GL_FLOAT, GL_FALSE, sizeof(DatosVertice), (void*) (12 * sizeof(float)));

// TEXTURE2D
	if (with_texture) {

		glActiveTexture(GL_TEXTURE0);
		glBindTexture(GL_TEXTURE_2D, this->texture);
		location = glGetUniformLocation(program, "Texture");
		if (location >= 0) {
			glUniform1i(location, 0);
		}
		location = glGetUniformLocation(program, "with_texture");
		if (location >= 0) {
			glUniform1i(location, 1);
		}
	} else {
		location = glGetUniformLocation(program, "with_texture");
		if (location >= 0) {
			glUniform1i(location, 0);
		}
	}

	//void * offset;

	glDrawElements(this->type, index_size, GL_UNSIGNED_INT, 0);

	//offset =

	glDrawElements(GL_TRIANGLE_FAN, size_tapa, GL_UNSIGNED_INT, (void*) (index_size * sizeof(unsigned int)));

	glDrawElements(GL_TRIANGLE_FAN, size_tapa, GL_UNSIGNED_INT,
			(void *) ((index_size + size_tapa) * sizeof(unsigned int)));

	glDisableVertexAttribArray(vertex);
	glDisableVertexAttribArray(normal);
	glDisableVertexAttribArray(tangent);
	glDisableVertexAttribArray(color);
	glDisableVertexAttribArray(UV);

	if (with_texture) {
		glBindTexture(GL_TEXTURE_2D, 0);
	}

	glBindBuffer(GL_ARRAY_BUFFER, 0);
	glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);

	/*glEnableClientState(GL_VERTEX_ARRAY);
	 glEnableClientState(GL_NORMAL_ARRAY);

	 glVertexPointer(3, GL_FLOAT, 0, vertex_buffer);
	 glNormalPointer(GL_FLOAT, 0, normal_buffer);

	 glDrawElements(GL_TRIANGLE_STRIP, index_buffer_size, GL_UNSIGNED_INT, index_buffer);

	 glNormalPointer(GL_FLOAT, 0, normal_buffer_tapas);
	 glDrawElements(GL_TRIANGLES, index_tapas_size, GL_UNSIGNED_INT, index_tapas);

	 if (this->show_normals) {
	 draw_normals();
	 draw_normals(normal_buffer_tapas, index_tapas, index_tapas_size);
	 }
	 glDisableClientState(GL_VERTEX_ARRAY);
	 glDisableClientState(GL_NORMAL_ARRAY);*/
}
